import { Repository, SelectQueryBuilder } from "typeorm";

import type {
  ConditionList,
  ConditionMatchBool,
  ConditionMatchEnum,
  ConditionMatchEnumOrs,
  ConditionMatchInteger,
  ConditionMatchIntegerOrs,
  ConditionMatchStr,
  ConditionMatchStrOrs,
  ConditionRegExpStr,
  ConditionSort,
  ConditionTime,
} from "qqlx-core";
import { ConditionType, Sortable } from "qqlx-core";
import { getSetData, isStringNotValid, isStringValid, toBoolean, toNumber, toString } from "qqlx-cdk";

import { Constructable } from "./dao.com.interface";

export type DaoOption<T extends object> = {
  repository: Repository<T>;
  relations_name: string;

  /** 说明在查询的时候，需要把布尔类型换成 0 和 1 */
  is_tiny_boolean?: boolean;
};

export class DaoBase<T extends object> implements DaoOption<T> {
  /** 注意事项
   * @insert 不会触发 Column 里的 ValueTransformer
   * @save 之后返回的是输入的 schema 但是带上了正确的主键
   */
  repository: Repository<T>;
  relations_name: string;
  is_tiny_boolean: boolean;

  constructor(option: DaoOption<T>) {
    this.relations_name = option.relations_name;
    this.repository = option.repository;
    this.is_tiny_boolean = option.is_tiny_boolean ?? false;
  }

  // #region 设置查询条件

  getSchema() {
    const Construct = this.repository.target as Constructable<T>;
    return new Construct();
  }

  getQueryBuilder(): SelectQueryBuilder<T> {
    return this.repository.createQueryBuilder(this.relations_name);
  }

  shakeConditionList(conditionLs: ConditionList<T>): ConditionList<T> {
    const seen = new Set<string>(); // 使用 Set 替代字符串拼接
    const resultLs: ConditionList<T> = [];
    const { propertiesMap } = this.repository.metadata;

    // 单次遍历，从后往前处理（保证后面的条件覆盖前面的）
    for (let i = structuredClone(conditionLs).length - 1; i >= 0; i--) {
      const condi = conditionLs[i];
      const key = condi.key as string;

      // 1. 验证关键词
      if (!isStringValid(key) || !propertiesMap[key]) continue;

      // 2. 检查重复（使用 Set，O(1) 查找）
      const uniqueKey = `${key}:${condi.type}`;
      if (seen.has(uniqueKey)) continue;

      seen.add(uniqueKey);
      resultLs.unshift(condi); // 从前面插入，保持顺序
    }

    return resultLs;
  }

  setCondition(qb: SelectQueryBuilder<T>, conditionList: ConditionList<T>, withSort = true) {
    const cons = this.shakeConditionList(conditionList);

    for (const con of cons) {
      switch (con.type) {
        case ConditionType.MatchStr: {
          this.setMatchStr(qb, con);
          break;
        }

        case ConditionType.MatchStrOrs: {
          this.setMatchStrOrs(qb, con);
          break;
        }

        case ConditionType.MatchBool: {
          this.setMatchBool(qb, con);
          break;
        }

        case ConditionType.MatchEnum: {
          this.setMatchEnum(qb, con);
          break;
        }

        case ConditionType.MatchEnumOrs: {
          this.setMatchEnumOrs(qb, con);
          break;
        }

        case ConditionType.MatchInteger: {
          this.setInteger(qb, con);
          break;
        }

        case ConditionType.MatchIntegerOrs: {
          this.setIntegerOrs(qb, con);
          break;
        }

        case ConditionType.RegExpStr: {
          this.setRegExpStr(qb, con);
          break;
        }

        case ConditionType.Time: {
          this.setConditionTime(qb, con);
          break;
        }
      }
    }

    // 排序要放在最后
    if (withSort) {
      const sortOnly = cons.find((c) => c.type === ConditionType.Sort);
      if (sortOnly) this.setConditionSort(qb, sortOnly);
    }
  }

  // #endregion

  // #region 设置查询条件：32位整数

  setIntegerOrs(qb: SelectQueryBuilder<T>, condition: ConditionMatchIntegerOrs<T>) {
    const rela = this.relations_name;
    const { key, value, isMatch } = condition;
    const _key = key as string;

    if (!Array.isArray(value)) throw new Error(`查询条件不正确（${rela}.${_key}）IntegerOrs`);
    const { values, count } = getSetData(value.map((v) => toNumber(v)));
    if (count === 0) {
      throw new Error(`缺少查询条件（${rela}.${_key}）IntegerOrs`);
    }

    const sql = `${rela}.${_key} ${isMatch ? "" : "NOT"} IN (:...${_key})`;
    qb.andWhere(sql, { [_key]: values });
  }

  setInteger(qb: SelectQueryBuilder<T>, condition: ConditionMatchInteger<T>) {
    const { key, value, isMatch } = condition;
    const _key = key as string;

    const sql = `${this.relations_name}.${_key} ${isMatch ? "" : "!"}= :${_key}`;
    qb.andWhere(sql, { [_key]: value });
  }

  // #endregion

  // #region 设置查询条件：枚举

  setMatchEnumOrs(qb: SelectQueryBuilder<T>, condition: ConditionMatchEnumOrs<T>) {
    const rela = this.relations_name;
    const { key, value, isMatch } = condition;
    const _key = key as string;

    if (!Array.isArray(value)) throw new Error(`查询条件不正确（${rela}.${_key}）MatchEnumOrs`);
    const processedValues: string[] = [];
    for (const v of value) {
      const str = toString(v);
      if (str.length > 0) processedValues.push(str);
    }

    const { values, count } = getSetData(processedValues);
    if (count === 0) {
      throw new Error(`缺少查询条件（${rela}.${_key}）MatchEnumOrs`);
    }

    const sql = `${rela}.${_key} ${isMatch ? "" : "NOT"} IN (:...${_key})`;
    qb.andWhere(sql, { [_key]: values });
  }

  setMatchEnum(qb: SelectQueryBuilder<T>, condition: ConditionMatchEnum<T>) {
    const rela = this.relations_name;
    const { key, value, isMatch } = condition;
    const _key = key as string;

    const sql = `${rela}.${_key} ${isMatch ? "" : "!"}= :${_key}`;
    qb.andWhere(sql, { [_key]: toNumber(value) });
  }

  // #endregion

  // #region 设置查询条件：字符串

  setRegExpStr(qb: SelectQueryBuilder<T>, condition: ConditionRegExpStr<T>) {
    const rela = this.relations_name;
    const { key, value, isMatch } = condition;
    const _key = key as string;

    const regexp = toString(value);
    if (isStringNotValid(regexp)) return;

    const sql = `${rela}.${_key} ${isMatch ? "" : "NOT"} LIKE :${_key}`;
    qb.andWhere(sql, { [_key]: `%${regexp}%` });
  }

  setMatchStrOrs(qb: SelectQueryBuilder<T>, condition: ConditionMatchStrOrs<T>) {
    const rela = this.relations_name;
    const { key, value, isMatch } = condition;
    const _key = key as string;

    if (!Array.isArray(value)) throw new Error(`查询条件不正确（${rela}.${_key}）MatchStrOrs`);
    const { values, count } = getSetData(value.map((v) => toString(v)).filter((v) => v.length > 0));
    if (count === 0) {
      throw new Error(`缺少查询条件（${rela}.${_key}）MatchStrOrs`);
    }

    const sql = `${rela}.${_key} ${isMatch ? "" : "NOT"} IN (:...${_key})`;
    qb.andWhere(sql, { [_key]: values });
  }

  setMatchStr(qb: SelectQueryBuilder<T>, condition: ConditionMatchStr<T>) {
    const rela = this.relations_name;
    const { key, value, isMatch } = condition;
    const _key = key as string;

    if (isStringNotValid(value)) {
      throw new Error(`缺少查询条件（${rela}.${_key}）MatchStr`);
    }

    const sql = `${rela}.${_key} ${isMatch ? "" : "!"}= :${_key}`;
    qb.andWhere(sql, { [_key]: value });
  }

  // #endregion

  // #region 设置查询条件：布尔值

  setMatchBool(qb: SelectQueryBuilder<T>, condition: ConditionMatchBool<T>) {
    const rela = this.relations_name;
    const { key, value } = condition;
    const _key = key as string;

    const sql = `${rela}.${_key} = :${_key}`;

    const bool = toBoolean(value);
    qb.andWhere(sql, { [_key]: this.is_tiny_boolean ? (bool ? 1 : 0) : bool });
  }

  // #endregion

  // #region 设置查询条件：排序

  setConditionSort(qb: SelectQueryBuilder<T>, condition: ConditionSort<T>) {
    const rela = this.relations_name;
    const { key, value } = condition;
    const _key = key as string;

    qb.orderBy(`${rela}.${_key}`, value === Sortable.ASC ? "ASC" : "DESC");
  }

  // #endregion

  // #region 设置查询条件：时间

  setConditionTime(qb: SelectQueryBuilder<T>, condition: ConditionTime<T>) {
    const rela = this.relations_name;
    const { key, value } = condition;
    const _key = key as string;

    const sql = `${rela}.${_key} BETWEEN :startTime AND :endTime`;
    qb.andWhere(sql, {
      startTime: BigInt(Math.min(Number(value.startTime), Number(value.endTime))).toString(),
      endTime: BigInt(Math.max(Number(value.startTime), Number(value.endTime))).toString(),
    });
  }

  // #endregion
}
